package agents;

import static base.Environment.Configurations.QUOTA_DEC_THRESHOLD;
import static base.Environment.Configurations.QUOTA_INC_THRESHOLD;
import static base.Environment.Configurations.VARY_QUOTA_FACTOR;

import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import selectors.StrategySelector;
import strategies.Strategy;
import strategies.StrategyGenerator;
import base.Environment;
import base.Environment.Configurations;

/**
 * An agent that uses dynamic strategy.
 */
public class AdaptiveAgent extends Agent {
	 
	protected StrategyGenerator gen;
	protected boolean varyQuotaFactor;
	protected double quotaIncThreshold;
	protected double quotaDecThreshold;
	protected double quotaIncFactor;
	protected double quotaDecFactor;
	
	protected double minQuota = 1D;
	
	LinkedList<Double> l = new LinkedList<Double>();
	
	public AdaptiveAgent(int quota, double quotaFactor, Environment env, StrategySelector selector,
						 StrategyGenerator gen) {
		
		super(quota, env, selector);
		this.gen               = gen;
		this.varyQuotaFactor   = env.getBoolean(VARY_QUOTA_FACTOR);
		this.quotaIncThreshold = env.getDouble(QUOTA_INC_THRESHOLD);
		this.quotaDecThreshold = env.getDouble(QUOTA_DEC_THRESHOLD);
		this.quotaIncFactor    = quotaFactor;
		this.quotaDecFactor    = quotaFactor;
		this.strategies.put(gen.genStrategy(this), new AverageUtility());
	}
	
	public AdaptiveAgent(int id, int quota, double quotaFactor, Environment env,
						 StrategySelector selector, StrategyGenerator gen) {
		
		super(id, quota, env, selector);
		this.gen               = gen;
		this.varyQuotaFactor   = env.getBoolean(VARY_QUOTA_FACTOR);
		this.quotaIncThreshold = env.getDouble(QUOTA_INC_THRESHOLD);
		this.quotaDecThreshold = env.getDouble(QUOTA_DEC_THRESHOLD);
		this.quotaIncFactor    = quotaFactor;
		this.quotaDecFactor    = quotaFactor;
		this.strategies.put(gen.genStrategy(this), new AverageUtility());
	}
	
	
	@Override
	public double harvest() {
		double utility = 0D;
		try {
			utility = super.harvest();
		} catch (RuntimeException e) {
			System.err.println(toString());
			throw e;
		}
		return utility;
	}
	
	/**
	 * Updates this agent's quota based on its performance and its implementation.
	 */
	@Override
	public void updateQuota() {
		double newQuota = quota;
		
		l.add(getQuotaFulfillment());
		
		if (getQuotaFulfillment() >= quotaIncThreshold) {
			newQuota *= 1d + quotaIncFactor;
			if (varyQuotaFactor) {
				quotaDecFactor = quotaIncFactor / 2D;
				quotaIncFactor *= 1d + quotaIncFactor;
			}
		}
		else if (getQuotaFulfillment() < quotaDecThreshold) {
			newQuota *= 1d - quotaDecFactor;
			if (varyQuotaFactor) {
				quotaIncFactor = quotaDecFactor / 2D;
				quotaDecFactor *= 1d + quotaDecFactor;
			}
		}
		
		if (newQuota <= minQuota)
			newQuota = minQuota;
		quota = newQuota;
		
		clearRecords();
	}
	
	@Override
	public void displayTopStrategy(){
		System.out.println(l);
		super.displayTopStrategy();
	}
	
	/**
	 * Updates this agent's strategies' harvesting targets according to its implementation.
	 */
	@Override
	public void updateStrategiesTargets() {
		gen.updateStrategiesTargets(this);
	}
	
	/**
	 * Updates this agent's strategies' harvesting paths according to its implementation.
	 */
	@Override
	public void updateStrategiesPaths() {
		// delete strategies that have performed relatively bad
		List<Strategy> delStrategies = gen.pickStrategiesToDelete(this);
		for (Strategy strategy : delStrategies) {
			System.out.println(this + " - deleting strategy: " + strategy);
			getStrategies().remove(strategy);
		}
		
		int maxStrategies = env.getInt(Configurations.MAX_STRAT_NO);
		
		// remove extra strategies - if it is the case
		if (getStrategies().size() > maxStrategies) {
			LinkedList<Entry<Strategy, AverageUtility>> sorted =
					getSortedStrategies(AverageUtility.INCREASING_UTILITY_COMPARATOR);
			
			while (getStrategies().size() > maxStrategies) {
				Strategy strategy = sorted.poll().getKey();
				System.out.println(this + " - deleting strategy: " + strategy);
				getStrategies().remove(strategy);
			}
		}
		
		int missingStrategies = maxStrategies - getStrategies().size();
		if (missingStrategies == 0) {
			return;
		}
		
		int strategiesToUpdate = (int) (getQuotaFulfillment() * missingStrategies);
		
		// add updated strategies
		List<Strategy> newStrategies = gen.updateStrategiesPaths(this, strategiesToUpdate);
		for (Strategy strategy : newStrategies) {
			System.out.println(this + " - adding updated strategy: " + strategy);
			getStrategies().put(strategy, new AverageUtility());
		}
		
		// add completely new strategies
		while (getStrategies().size() < maxStrategies) {
			Strategy strategy = gen.genStrategy(this);
			System.out.println(this + " - adding new strategy: " + strategy);
			getStrategies().put(strategy, new AverageUtility());
		}
	}
}
